﻿#pragma once

#include "lua/lua.hpp"
#include "../../http/http_call.hh"
#include "../box_std_allocator.hh"

#include <functional>
#include <list>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>

namespace google {
namespace protobuf {
class Reflection;
class Message;
class Descriptor;
class FieldDescriptor;
class FileDescriptor;
class DynamicMessageFactory;
class Arena;
namespace compiler {
class Importer;
class DiskSourceTree;
} // namespace compiler
} // namespace protobuf
} // namespace google

namespace kratos {
namespace util {
struct WheelNode;
}
} // namespace kratos

namespace kratos {
namespace time {
class LocalDate;
}
} // namespace kratos

namespace kratos {
namespace http {
class HttpCall;
}
} // namespace kratos

using ProtobufReflection = google::protobuf::Reflection;
using ProtobufMessage = google::protobuf::Message;
using ProtobufFieldDescriptor = google::protobuf::FieldDescriptor;
using ProtobufImporter = google::protobuf::compiler::Importer;
using ProtobufFileDescriptor = google::protobuf::FileDescriptor;
using ProtobufDescriptor = google::protobuf::Descriptor;
using ProtobufDynamicMessageFactory = google::protobuf::DynamicMessageFactory;
using ProtobufArena = google::protobuf::Arena;
using ProtobufDiskSourceTree = google::protobuf::compiler::DiskSourceTree;

namespace kratos {
namespace lua {

/**
 * 协程状态
 */
enum class ThreadState {
  NONE = 0,
  READY = 1, ///< 就绪，可以立刻运行
  YIELD,     ///< 出让CPU
  DEAD,      ///< 死亡，等待销毁
};

class LuaThread;

using ThreadPtr = std::shared_ptr<LuaThread>;

/**
 * Lua工具方法
 */
namespace LuaUtil {

/**
 * @brief 从栈顶获取数据，类型为std::int16_t
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::int16_t &arg);
/**
 * @brief 从栈顶获取数据，类型为std::int32_t
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::int32_t &arg);
/**
 * @brief 从栈顶获取数据，类型为std::int64_t
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::int64_t &arg);
/**
 * @brief 从栈顶获取数据，类型为std::uint16_t
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::uint16_t &arg);
/**
 * @brief 从栈顶获取数据，类型为std::uint32_t
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::uint32_t &arg);
/**
 * @brief 从栈顶获取数据，类型为std::uint64_t
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::uint64_t &arg);
/**
 * @brief 从栈顶获取数据，类型为bool
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, bool &arg);
/**
 * @brief 从栈顶获取数据，类型为std::string
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::string &arg);
/**
 * @brief 从栈顶获取数据，类型为float
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, float &arg);
/**
 * @brief 从栈顶获取数据，类型为double
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, double &arg);
/**
 * @brief 从栈顶获取数据，类型为ProtobufMessage
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, ProtobufMessage &arg);
/**
 * @brief 从栈顶获取数据，类型为kratos::http::HttpCall
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, kratos::http::HttpCall &call);
/**
 * @brief 从栈顶获取数据，类型为kratos::service::PoolUnorederedMap<std::string,
 * std::string>
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(
    lua_State *state,
    kratos::service::PoolUnorederedMap<std::string, std::string> &map);
/**
 * @brief 从栈顶获取数据，类型为kratos::http::HeaderMap
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, kratos::http::HeaderMap &map);
/**
 * @brief 从栈顶获取数据，类型为std::vector<std::string>
 * @param state lua虚拟机/协程
 * @param [OUT] arg 获取的数据
 * @return true或false
 */
extern bool lua_pop_result(lua_State *state, std::vector<std::string> &vec);
/**
 * @brief 从栈的index位置处, 获取数据，只支持负索引
 * @tparam T 获取的类型
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 获取的数据
 */
template <typename T> inline T get(lua_State *L, int index) {
  throw std::runtime_error("Unsupported type");
}
/**
 * @brief 从栈的index位置处, 获取整数，只支持负索引
 * @tparam T 获取的类型
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 获取的数据
 */
template <typename T> inline T get_integer(lua_State *L, int index) {
  if (!lua_isinteger(L, index)) {
    throw std::runtime_error("Not a lua integer type");
  }
  return (T)lua_tointeger(L, index);
}
/**
 * @brief 获取bool
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline bool get(lua_State *L, int index) {
  return (lua_toboolean(L, index) == 1);
}
/**
 * @brief 获取int
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline int get(lua_State *L, int index) {
  return get_integer<int>(L, index);
}
/**
 * @brief 获取long
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline long get(lua_State *L, int index) {
  return get_integer<long>(L, index);
}
/**
 * @brief 获取long long
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline long long get(lua_State *L, int index) {
  return get_integer<long long>(L, index);
}
/**
 * @brief 获取unsigned int
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline unsigned int get(lua_State *L, int index) {
  return get_integer<unsigned int>(L, index);
}
/**
 * @brief 获取unsigned long
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline unsigned long get(lua_State *L, int index) {
  return get_integer<unsigned long>(L, index);
}
/**
 * @brief 获取unsigned long long
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline unsigned long long get(lua_State *L, int index) {
  return get_integer<unsigned long long>(L, index);
}
/**
 * @brief 获取short
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline short get(lua_State *L, int index) {
  return get_integer<short>(L, index);
}
/**
 * @brief 获取unsigned short
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline unsigned short get(lua_State *L, int index) {
  return get_integer<unsigned short>(L, index);
}
/**
 * @brief 获取char
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline char get(lua_State *L, int index) {
  return get_integer<char>(L, index);
}
/**
 * @brief 获取unsigned char
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline unsigned char get(lua_State *L, int index) {
  return get_integer<unsigned char>(L, index);
}
/**
 * @brief 获取const char*
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline const char *get(lua_State *L, int index) {
  if (!lua_isstring(L, index)) {
    throw std::runtime_error("Not a lua string type");
  }
  return lua_tostring(L, index);
}
/**
 * @brief 获取std::string
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline std::string get(lua_State *L, int index) {
  if (!lua_isstring(L, index)) {
    throw std::runtime_error("Not a lua string type");
  }
  return lua_tostring(L, index);
}
/**
 * @brief 获取double
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline double get(lua_State *L, int index) {
  if (!lua_isnumber(L, index)) {
    throw std::runtime_error("Not a lua number type");
  }
  return (double)lua_tonumber(L, index);
}
/**
 * @brief 获取float
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline float get(lua_State *L, int index) {
  if (!lua_isnumber(L, index)) {
    throw std::runtime_error("Not a lua number type");
  }
  return (float)lua_tonumber(L, index);
}
/**
 * @brief 获取std::vector<std::string>
 * @param L lua虚拟机/协程
 * @param index 栈位置，只支持负索引
 * @return 值
 */
template <> inline std::vector<std::string> get(lua_State *L, int index) {
  if (!lua_istable(L, index)) {
    throw std::runtime_error("Not a lua table type");
  }
  if (index < -1) {
    lua_pushvalue(L, index);
  }
  std::vector<std::string> values;
  LuaUtil::lua_pop_result(L, values);
  return values;
}
/**
 * google::protobuf::Message转化为Lua表并压栈
 *
 * \param L Lua虚拟机
 * \param msg google::protobuf::Message
 */
extern void pbmsg_to_lua_table(lua_State *L, const ProtobufMessage &msg);
/**
 * 将Lua栈顶元素转化为google::protobuf::Message
 *
 * \param L Lua虚拟机
 * \param [OUT] msg google::protobuf::Message
 * \return true或false
 */
extern bool lua_table_to_pbmsg(lua_State *L, ProtobufMessage &msg);
/**
 * google::protobuf::Message内repeated字段导入Lua表字段
 *
 * \param L Lua虚拟机
 * \param msg google::protobuf::Message
 * \param field 字段描述
 */
extern void on_repeated_field(lua_State *L, const ProtobufMessage &msg,
                              const ProtobufFieldDescriptor *field);
/**
 * @brief 导入map内pair到lua表
 * @param L Lua虚拟机
 * @param msg google::protobuf::Message
 * @param field 字段描述
 */
extern void on_map_field(lua_State *L, const ProtobufMessage &msg,
                         const ProtobufFieldDescriptor *field);
extern void on_map_field_key(lua_State *L, const ProtobufMessage &msg,
                             const ProtobufFieldDescriptor *field);
extern void on_map_field_value(lua_State *L, const ProtobufMessage &msg,
                               const ProtobufFieldDescriptor *field);
extern void on_field(lua_State *L, const ProtobufMessage &msg,
                     const ProtobufFieldDescriptor *field);
extern bool on_lua_repeated_value(lua_State *L, ProtobufMessage &msg,
                                  const ProtobufReflection *reflection,
                                  const ProtobufFieldDescriptor *field,
                                  int index);
extern bool on_lua_table_value(lua_State *L, ProtobufMessage &msg,
                               const ProtobufReflection *reflection,
                               const ProtobufFieldDescriptor *field, int index);
extern bool on_lua_map(lua_State *L, ProtobufMessage &msg,
                       const ProtobufReflection *reflection,
                       const ProtobufFieldDescriptor *field, int index);
extern void on_lua_string_map(
    lua_State *L,
    kratos::service::PoolUnorederedMap<std::string, std::string> map);

extern void
on_lua_string_map(lua_State *L,
                  std::unordered_map<std::string, std::string> &map);

extern void lua_push(lua_State *state, std::int16_t arg);

extern void lua_push(lua_State *state, std::uint16_t arg);

extern void lua_push(lua_State *state, std::int32_t arg);

#ifdef __linux__
extern void lua_push(lua_State *state, long long int arg);
#endif // __linux__

extern void lua_push(lua_State *state, std::int64_t arg);

extern void lua_push(lua_State *state, std::uint32_t arg);

extern void lua_push(lua_State *state, std::uint64_t arg);

extern void lua_push(lua_State *state, bool arg);

extern void lua_push(lua_State *state, float arg);

extern void lua_push(lua_State *state, double arg);

extern void lua_push(lua_State *state, const char *arg);

extern void lua_push(lua_State *state, const ProtobufMessage &arg);

extern void lua_push(lua_State *state, void *arg);

extern void lua_push(lua_State *state, int *arg);

extern void lua_push(lua_State *state, const std::string &arg);

extern void lua_push(lua_State *state, std::string &&arg);

extern void lua_push(lua_State *state,
                     const kratos::time::LocalDate &local_date);

extern void lua_push(lua_State *state, kratos::http::HttpCall *call);

template <typename T>
inline void push_table(lua_State *l, const std::vector<T> &v) {
  lua_createtable(l, (int)v.size(), 0);
  for (int i = 0; i < (int)v.size(); i++) {
    lua_pushinteger(l, i + 1);
    lua_push(l, v[i]);
    lua_settable(l, -3);
  }
}

template <typename T>
inline void push_table(lua_State *l, const std::list<T> &v) {
  lua_createtable(l, (int)v.size(), 0);
  int i = 0;
  for (auto &o : v) {
    lua_pushinteger(l, i + 1);
    lua_push(l, o);
    lua_settable(l, -3);
    i += 1;
  }
}

template <typename K, typename V>
inline void push_table(lua_State *l, const std::unordered_map<K, V> &v) {
  lua_createtable(l, 0, (int)v.size());
  for (auto &[k, v] : v) {
    lua_push(l, k);
    lua_push(l, v);
    lua_settable(l, -3);
  }
}

template <typename T>
inline void push_table(lua_State *l, const kratos::service::PoolVector<T> &v) {
  lua_createtable(l, (int)v.size(), 0);
  for (int i = 0; i < (int)v.size(); i++) {
    lua_pushinteger(l, i + 1);
    lua_push(l, v[i]);
    lua_settable(l, -3);
  }
}

template <typename T>
inline void push_table(lua_State *l, const kratos::service::PoolList<T> &v) {
  lua_createtable(l, (int)v.size(), 0);
  int i = 0;
  for (auto &o : v) {
    lua_pushinteger(l, i + 1);
    lua_push(l, o);
    lua_settable(l, -3);
    i += 1;
  }
}

template <typename K, typename V>
inline void push_table(lua_State *l,
                       const kratos::service::PoolUnorederedMap<K, V> &v) {
  lua_createtable(l, 0, (int)v.size());
  for (auto &[k, v] : v) {
    lua_push(l, k);
    lua_push(l, v);
    lua_settable(l, -3);
  }
}

template <typename T>
inline void lua_push(lua_State *state, const std::vector<T> &v) {
  push_table(state, v);
}

template <typename T>
inline void lua_push(lua_State *state, const std::list<T> &v) {
  push_table(state, v);
}

template <typename K, typename V>
inline void lua_push(lua_State *state, const std::unordered_map<K, V> &m) {
  push_table(state, m);
}

template <typename T>
inline void lua_push(lua_State *state,
                     const kratos::service::PoolVector<T> &v) {
  push_table(state, v);
}

template <typename T>
inline void lua_push(lua_State *state, const kratos::service::PoolList<T> &v) {
  push_table(state, v);
}

template <typename K, typename V>
inline void lua_push(lua_State *state,
                     const kratos::service::PoolUnorederedMap<K, V> &m) {
  push_table(state, m);
}

/**
 * Lua压栈工具类
 */
struct PushMethod {
  lua_State *L_{nullptr};
  PushMethod(lua_State *L) { L_ = L; }
  void operator()(std::int16_t v) { lua_push(L_, v); }
  void operator()(std::uint16_t v) { lua_push(L_, v); }
  void operator()(std::int32_t v) { lua_push(L_, v); }
  void operator()(std::int64_t v) { lua_push(L_, v); }
  void operator()(std::uint32_t v) { lua_push(L_, v); }
  void operator()(std::uint64_t v) { lua_push(L_, v); }
  void operator()(float v) { lua_push(L_, v); }
  void operator()(double v) { lua_push(L_, v); }
  void operator()(bool v) { lua_push(L_, v); }
  void operator()(const char *v) { lua_push(L_, v); }
  void operator()(const ProtobufMessage &v) { lua_push(L_, v); }
  void operator()(const std::string &v) { lua_push(L_, v); }
  void operator()(kratos::http::HttpCall *call) { lua_push(L_, call); }
  void operator()(const kratos::time::LocalDate &local_date) {
    lua_push(L_, local_date);
  }
  void operator()(kratos::time::LocalDate *local_date) {
    lua_push(L_, *local_date);
  }
  template <typename T> void operator()(const std::vector<T> &v) {
    push_table(L_, v);
  }
  template <typename T> void operator()(const std::list<T> &v) {
    push_table(L_, v);
  }
  template <typename K, typename V>
  void operator()(const std::unordered_map<K, V> &v) {
    push_table(L_, v);
  }
  template <typename T>
  void operator()(const kratos::service::PoolVector<T> &v) {
    push_table(L_, v);
  }
  template <typename T> void operator()(const kratos::service::PoolList<T> &v) {
    push_table(L_, v);
  }
  template <typename K, typename V>
  void operator()(const kratos::service::PoolUnorederedMap<K, V> &v) {
    push_table(L_, v);
  }
};

extern void push_table_bool(lua_State *L, const ProtobufMessage &msg,
                            const ProtobufFieldDescriptor *field);

extern void push_table_int32(lua_State *L, const ProtobufMessage &msg,
                             const ProtobufFieldDescriptor *field);

extern void push_table_uint32(lua_State *L, const ProtobufMessage &msg,
                              const ProtobufFieldDescriptor *field);

extern void push_table_int64(lua_State *L, const ProtobufMessage &msg,
                             const ProtobufFieldDescriptor *field);

extern void push_table_uint64(lua_State *L, const ProtobufMessage &msg,
                              const ProtobufFieldDescriptor *field);

extern void push_table_float(lua_State *L, const ProtobufMessage &msg,
                             const ProtobufFieldDescriptor *field);

extern void push_table_double(lua_State *L, const ProtobufMessage &msg,
                              const ProtobufFieldDescriptor *field);

extern void push_table_string(lua_State *L, const ProtobufMessage &msg,
                              const ProtobufFieldDescriptor *field);

extern void push_table_enum(lua_State *L, const ProtobufMessage &msg,
                              const ProtobufFieldDescriptor *field);

extern void set_field_string(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void set_field_int32(lua_State *L, ProtobufMessage &msg,
                            const ProtobufReflection *reflection,
                            const ProtobufFieldDescriptor *field, int index);

extern void set_field_int64(lua_State *L, ProtobufMessage &msg,
                            const ProtobufReflection *reflection,
                            const ProtobufFieldDescriptor *field, int index);

extern void set_field_uint32(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void set_field_uint64(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void set_field_bool(lua_State *L, ProtobufMessage &msg,
                           const ProtobufReflection *reflection,
                           const ProtobufFieldDescriptor *field, int index);

extern void set_field_float(lua_State *L, ProtobufMessage &msg,
                            const ProtobufReflection *reflection,
                            const ProtobufFieldDescriptor *field, int index);

extern void set_field_double(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void set_field_enum(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void add_field_int32(lua_State *L, ProtobufMessage &msg,
                            const ProtobufReflection *reflection,
                            const ProtobufFieldDescriptor *field, int index);

extern void add_field_int64(lua_State *L, ProtobufMessage &msg,
                            const ProtobufReflection *reflection,
                            const ProtobufFieldDescriptor *field, int index);

extern void add_field_uint32(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void add_field_uint64(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void add_field_string(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void add_field_bool(lua_State *L, ProtobufMessage &msg,
                           const ProtobufReflection *reflection,
                           const ProtobufFieldDescriptor *field, int index);

extern void add_field_float(lua_State *L, ProtobufMessage &msg,
                            const ProtobufReflection *reflection,
                            const ProtobufFieldDescriptor *field, int index);

extern void add_field_double(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

extern void add_field_enum(lua_State *L, ProtobufMessage &msg,
                             const ProtobufReflection *reflection,
                             const ProtobufFieldDescriptor *field, int index);

/**
 * 获取Lua当前堆栈
 *
 * \param L Lua虚拟机
 * \param thread 协程
 * \return 堆栈信息
 */
extern std::string get_lua_traceback(lua_State *L, lua_State *thread = nullptr);
/**
 * 根据lua_resume的返回值获取错误信息
 *
 * \param L Lua虚拟机
 * \param status lua_resume的返回值
 * \param error 错误信息
 * \return true或false
 */
extern bool catch_lua_error(lua_State *L, int status, std::string &error);
/**
 * 根据lua_resume的返回值获取错误信息
 *
 * \param thread Lua协程
 * \param status lua_resume的返回值
 * \param error 错误信息
 * \return true或false
 */
extern bool catch_lua_error(LuaThread *thread, int status, std::string &error);

/**
 * Lua c function
 */
using LuaCFunction = std::function<int(lua_State *)>;

/**
 * Lua虚拟机内注册一个全局函数
 *
 * \param L Lua虚拟机
 * \param name 函数名
 * \param f C函数
 * \return true或false
 */
extern bool register_function(lua_State *L, const char *name, LuaCFunction f);

struct StackGuard {
  int top_{0};
  lua_State *L_{nullptr};

public:
  StackGuard(lua_State *l) {
    L_ = l;
    top_ = lua_gettop(l);
  }
  ~StackGuard() { lua_settop(L_, top_); }
};

extern ThreadState get_status(lua_State *L);

extern auto get_global_table(lua_State *L, const std::string &name) -> bool;

extern auto get_registry_table(lua_State *L, int *address) -> bool;

extern auto set_registry_table(lua_State *L) -> bool;

extern auto get_registry_value(lua_State *L, const char *key) -> bool;

extern auto new_registry_ref(lua_State *L) -> int;

extern auto remove_registry_ref(lua_State *L, int key) -> void;

extern auto new_thread(lua_State *L) -> lua_State *;

extern auto create_table(lua_State *L) -> bool;

extern auto new_registry_table(lua_State *L) -> int;

extern auto remove_registry_key(lua_State *L, int &reg_key) -> void;

template <typename K, typename V>
inline auto set_table(lua_State *l, K k, V v, int index) -> void {
  lua_push(l, k);
  lua_push(l, v);
  lua_settable(l, index);
}

struct _LuaNil {};

constexpr static _LuaNil LuaNil;

template <typename K>
inline auto set_table(lua_State *l, K k, _LuaNil v, int index) -> void {
  lua_push(l, k);
  lua_pushnil(l);
  lua_settable(l, index);
}

template <typename K>
inline auto remove_table(lua_State *l, K k, int index) -> void {
  set_table(l, k, LuaNil, index);
}

template <typename T> T inline pop_value(lua_State *state) {
  T t;
  if (!lua_pop_result(state, t)) {
    throw std::runtime_error("Cannot get value form lua stack");
  }
  lua_pop(state, 1);
  return t;
}

template <> inline util::WheelNode *pop_value(lua_State *state) {
  std::uint64_t t;
  if (!lua_pop_result(state, t)) {
    throw std::runtime_error("Cannot get value form lua stack");
  }
  lua_pop(state, 1);
  return (util::WheelNode *)t;
}

template <typename T> inline T pop_ptr_value(lua_State *state) {
  if (!lua_islightuserdata(state, -1)) {
    return nullptr;
  }
  auto t = (T)lua_touserdata(state, -1);
  lua_pop(state, 1);
  return t;
}

template <class T, class F, std::size_t... I>
inline constexpr F foreach_impl(T &&t, F &&f, std::index_sequence<I...>) {
  return (void)std::initializer_list<int>{
             (std::forward<F>(f)(std::get<I>(std::forward<T>(t))), 0)...},
         f;
}
/**
 * 对容器内所有元素调用f, 参数为容器元素
 */
template <class T, class F> inline constexpr F tuple_foreach(T &&t, F &&f) {
  return foreach_impl(
      std::forward<T>(t), std::forward<F>(f),
      std::make_index_sequence<
          std::tuple_size<std::remove_reference_t<T>>::value>{});
}

template <typename... ARGS>
inline bool call_lua_function_no_ret(const std::string &name, lua_State *L,
                                     std::string &error_string, ARGS... args) {
  if (!lua_getglobal(L, name.c_str())) {
    error_string = "method not found:" + name;
    return false;
  }
  auto tuple_args = std::make_tuple(args...);
  tuple_foreach(tuple_args, PushMethod(L));
  auto ret =
      lua_resume(L, nullptr, std::tuple_size<std::tuple<ARGS...>>::value);
  return catch_lua_error(L, ret, error_string);
}

template <typename RET, typename... ARGS>
inline bool call_lua_function(const std::string &name, lua_State *L, RET &ret,
                              std::string &error_string, ARGS... args) {
  if (!lua_getglobal(L, name.c_str())) {
    error_string = "method not found:" + name;
    return false;
  }
  auto tuple_args = std::make_tuple(args...);
  tuple_foreach(tuple_args, PushMethod(L));
  auto error =
      lua_resume(L, nullptr, std::tuple_size<std::tuple<ARGS...>>::value);
  if (!catch_lua_error(L, error, error_string)) {
    return false;
  }
  if (!lua_pop_result(L, ret)) {
    return false;
  }
  return true;
}

template <typename... ARGS>
inline bool resume(lua_State *L, std::string &error_string, ARGS... args) {
  auto tuple_args = std::make_tuple(args...);
  tuple_foreach(tuple_args, PushMethod(L));
  auto error =
      lua_resume(L, nullptr, std::tuple_size<std::tuple<ARGS...>>::value);
  if (!catch_lua_error(L, error, error_string)) {
    return false;
  }
  return true;
}

template <typename... ARGS>
inline bool resume_nargs(lua_State *L, std::string &error_string, int nargs) {
  auto error = lua_resume(L, nullptr, nargs);
  if (!catch_lua_error(L, error, error_string)) {
    return false;
  }
  return true;
}

/**
 * @brief 默认会向lua栈内push一个nil返回值
 */
struct NilPusher {
  lua_State *L_{nullptr};
  int top_{0};
  bool pushed{false};
  NilPusher(lua_State *l) { L_ = l; }
  ~NilPusher() noexcept(false) {
    if (!pushed) {
      throw std::runtime_error("NilPusher");
    }
  }
  int return_value() {
    lua_pushnil(L_);
    pushed = true;
    return 1;
  }
  int return_nil(int n) {
    for (int i = 0; i < n; i++) {
      lua_pushnil(L_);
    }
    pushed = true;
    return n;
  }
  template <typename... ARGS> int return_value(ARGS... args);
};

template <typename... ARGS> int NilPusher::return_value(ARGS... args) {
  auto tuple_args = std::make_tuple(args...);
  tuple_foreach(tuple_args, PushMethod(L_));
  pushed = true;
  return std::tuple_size<std::tuple<ARGS...>>::value;
}
/**
 * @brief 默认会向lua栈内push一个false返回值
 */
struct BoolPusher {
  lua_State *L_{nullptr};
  int top_{0};
  bool pushed{false};
  BoolPusher(lua_State *l) { L_ = l; }
  ~BoolPusher() noexcept(false) {
    if (!pushed) {
      throw std::runtime_error("NilPusher");
    }
  }
  int return_value() {
    lua_pushboolean(L_, 0);
    pushed = true;
    return 1;
  }
  template <typename T> int return_value(T t);
};

template <typename T> int BoolPusher::return_value(T t) {
  LuaUtil::lua_push(L_, t);
  pushed = true;
  return 1;
}

/**
 * @brief Lua虚拟机（协程）包装类
 */
class LuaState {
  int top_{0};            ///< 建立包装类时的栈内元素数量
  lua_State *L_{nullptr}; ///< 虚拟机

public:
  /**
   * @brief 构造
   * @param l Lua虚拟机
   */
  LuaState(lua_State *l);
  /**
   * @brief 构造
   * @param thread Lua协程
   */
  LuaState(LuaThread *thread);
  /**
   * @brief 构造
   * @param thread Lua协程包装类
   */
  LuaState(kratos::lua::ThreadPtr thread);
  /**
   * 析构
   */
  ~LuaState();
  /**
   * @brief 从栈的index位置获取值, index只支持负索引
   * @tparam T 值类型
   * @param index 栈的index位置
   * @return 值
   */
  template <typename T> T get(int index);
  /**
   * @brief 将t压入栈顶
   * @tparam T 值类型
   * @param t 值
   * @return 压入的元素数量，一定返回1
   */
  template <typename T> int push(T t);
  /**
   * @brief 从栈顶获取值
   * @tparam T 值类型
   * @param [OUT] t 返回的值
   * @return true或false
   */
  template <typename T> bool copy(T &t);
  /**
   * @brief 从栈index位置处获取值
   * @tparam T 值类型
   * @param [OUT] t 返回的值
   * @param index 栈位置，index只支持负索引
   * @return true或false
   */
  template <typename T> bool copy(T &t, int index);
  /**
   * @brief 将栈索引index处的值压到栈顶
   * @param index 栈索引
   * @return true或false
   */
  bool push_value(int index);
  /**
   * @brief 获取注册表内的值并压入栈顶
   * @param register_key 注册表内的key
   * @return true或false
   */
  bool get_reg_table(int register_key);
  /**
   * @brief 获取注册表内key对应的表内函数，并压入栈顶
   * @param register_key 注册表内的key
   * @param key 函数表内的key
   * @return true或false
   */
  bool get_lua_reg_function(int register_key, int key);
  /**
   * @brief 获取注册表内key对应的表内函数，并压入栈顶
   * @param register_key 注册表内的key
   * @param key 函数表内的key
   * @return true或false
   */
  bool get_lua_reg_function(int register_key, const std::string &key);
  /**
   * @brief 向注册表内key对应的表内添加一个函数
   * @param register_key 注册表内的key
   * @param key 函数表内的key
   * @return true或false
   */
  bool add_lua_reg_function(int register_key, int key);
  /**
   * @brief 向注册表内key对应的表内添加一个函数
   * @param register_key 注册表内的key
   * @param key 函数表内的key
   * @return true或false
   */
  bool add_lua_reg_function(int register_key, const std::string &key);
  /**
   * @brief 将表内key对应的函数删除，这个表存放于注册表内，键为register_key
   * @param register_key 注册表内的key
   * @param key 函数表内的key
   * @return true或false
   */
  bool remove_lua_reg_function(int register_key, int key);
  /**
   * @brief 将表内key对应的函数删除，这个表存放于注册表内，键为register_key
   * @param register_key 注册表内的key
   * @param key 函数表内的key
   * @return true或false
   */
  bool remove_lua_reg_function(int register_key, const std::string &key);
  /**
   * @brief 删除表内元素
   * @tparam K key类型
   * @param k key值
   * @param index 表在栈上的索引
   * @return true或false
  */
  template <typename K> bool remove_table(K k, int index);
  /**
   * @brief 设置表{key, value}
   * @tparam K key类型
   * @tparam V 值类型
   * @param k key
   * @param v value
   * @param index 表在栈上的索引
   * @return true或false
  */
  template <typename K, typename V> bool set_table(K k, V v, int index);
  int push_nil();
  bool isstring(int index) const;
  bool isinteger(int index) const;
  bool isnumber(int index) const;
  bool isfunction(int index) const;
  bool isuserdata(int index) const;
  bool istable(int index) const;
  bool isbool(int index) const;
  void pop(int count);
  /**
   * @brief 注册一个全局C函数
   * @param name 函数名
   * @param cfunc C函数指针
   * @return true或false
  */
  bool register_function(const char *name, LuaCFunction cfunc);
  /**
   * @brief 重新设置栈顶到刚建立LuaState时保存的栈顶位置
  */
  void restore_stack();
};
template <typename T> T LuaState::get(int index) {
  return LuaUtil::get<T>(L_, index);
}
template <typename T> int LuaState::push(T t) {
  PushMethod method(L_);
  method(std::forward<T>(t));
  return 1;
}
template <typename T> bool LuaState::copy(T &t) {
  return LuaUtil::lua_pop_result(L_, t);
}
template <typename T> bool LuaState::copy(T &t, int index) {
  if (index < -1) {
    lua_pushvalue(L_, index);
  }
  auto result = LuaUtil::lua_pop_result(L_, t);
  if (index < -1) {
    lua_pop(L_, 1);
  }
  return result;
}
template <typename K> bool LuaState::remove_table(K k, int index) {
  LuaUtil::set_table(L_, k, LuaNil, index);
  return true;
}
template <typename K, typename V>
bool LuaState::set_table(K k, V v, int index) {
  LuaUtil::set_table(L_, k, v, index);
  return true;
}

} // namespace LuaUtil
} // namespace lua
} // namespace kratos
